package com.sayice.gomoku.pojo;

import com.sayice.gomoku.game.Action;
import com.sayice.gomoku.game.Game;
import com.sayice.gomoku.game.Match;
import com.sayice.gomoku.service.UserService;
import com.sayice.gomoku.service.impl.UserServiceImpl;
import com.sayice.gomoku.utils.JdbcUtils;

import javax.websocket.Session;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * 玩家
 */
public class Player {
    private final UserService userService = new UserServiceImpl();
    
    /**
     * 玩家编号
     */
    private int id;
    
    /**
     * 玩家名
     */
    private String name;
    
    /**
     * 玩家 session
     */
    private Session session;
    
    /**
     * 所在游戏房间
     */
    private Room room;
    
    /**
     * 是否正在匹配
     */
    private boolean matching;
    
    public String getName() {
        return name;
    }
    
    public void setRoom(Room room) {
        this.room = room;
    }
    
    /**
     * 上线
     *
     * @param name    玩家名
     * @param session 玩家 session
     */
    public void online(int id, String name, Session session) throws IOException {
        // 能够上线的条件为玩家未在对战系统中
        // 不满足条件时发送提示消息并关闭会话
        this.session = session;
        if (Game.playerMap.containsKey(name)) {
            send(Action.showMessage, "已在对战系统中，请勿重复进入");
            // 关闭会话
            session.close();
            return;
        }
        System.out.println(name + "进入对战系统");
        this.id = id;
        this.name = name;
        // 先上线再刷新空闲列表（setFree()中）
        setOnline(true);
        setFree(true, this);
    }
    
    /**
     * 下线
     */
    public void offline() throws IOException {
        // 能够下线的条件为玩家在对战系统中
        // 不满足条件时无响应
        if (name == null) {
            // online()里的session.close()执行后会经过 onClose() 到这里来
            return;
        }
        System.out.println(name + "退出对战系统");
        // 若正在匹配/观战/对局则先退出（方法中含判断逻辑）
        stopMatch();
        leaveWatch();
        leaveGame();
        // 先刷新空闲列表 (setFree()中) 再下线
        setFree(false, this);
        setOnline(false);
    }
    
    /**
     * 开始匹配
     */
    public void startMatch() throws IOException {
        // 能够开始匹配的条件为未在匹配中且未在对局中
        // 不满足条件时向客户端发送提示消息
        if (matching) {
            send(Action.showMessage, "匹配中，请稍后");
            return;
        }
        if (inGame()) {
            send(Action.showMessage, "请先完成当前对局");
            return;
        }
        // 设置匹配状态
        matching = true;
        // 将玩家添加至匹配列表
        Match.matchList.add(this);
        System.out.println("匹配列表长度: " + Match.matchList.size());
    }
    
    /**
     * 停止匹配
     */
    public void stopMatch() {
        // 能够停止匹配的条件为正在匹配
        // 不满足条件时无响应
        if (matching) {
            matching = false;
            // 将玩家从匹配列表移除
            Match.matchList.remove(this);
            System.out.println("匹配列表长度: " + Match.matchList.size());
        }
    }
    
    /**
     * 发起挑战
     *
     * @param target 目标玩家
     */
    public void sendChallenge(Player target) throws IOException {
        // 能够发起挑战请求的条件为双方都不在对局中，通过 cannotPlayWith 方法判断
        // 不满足条件时向客户端发送提示消息 (在 capPlayWith 中实现)
        if (!canPlayWith(target)) {
            return;
        }
        // 给对方发送消息
        target.send(Action.challenged, name);
    }
    
    /**
     * 处理挑战（接受/拒绝）
     *
     * @param target 目标玩家
     * @param accept 是否接受
     */
    public void handleChallenge(Player target, boolean accept) throws IOException {
        // 给对方发送消息
        target.send(Action.challengeReplied, new HashMap<String, Object>() {{
            put("name", name);
            put("accept", accept);
        }});
        if (accept) {
            // 能够开始游戏的条件为双方都不在对局中，通过 canPlayWith 方法判断
            // 不满足条件时向客户端发消息提示 (在 capPlayWith 中实现)
            if (canPlayWith(target)) {
                Game.createGame(this, target);
            }
        }
    }
    
    /**
     * 下子
     *
     * @param x x坐标
     * @param y y坐标
     */
    public void playChess(int x, int y) throws IOException {
        Player rival = room.getRival(this);
        // 若下子后获胜则 room.playChess 会返回 true
        if (room.playChess(this, x, y)) {
            send(Action.win, "恭喜获得胜利！");
            rival.send(Action.lose, "胜败乃兵家常事，继续努力吧！");
            // 关闭游戏房间
            room.close();
            // 增加获胜数
            addWinCount();
            return;
        }
        // 若没有玩家获胜且棋子已下满则判平局
        if (room.isChessFull()) {
            send(Action.tie, "平局");
            rival.send(Action.tie, "平局");
            // 关闭游戏房间
            room.close();
        }
    }
    
    /**
     * 请求悔棋
     */
    public void sendUndo() throws IOException {
        // 能够发起悔棋请求的条件是: 玩家在对局中且棋盘上有棋子且轮到对方下子了 (玩家刚下完子) 且玩家还未发起悔棋请求
        // 不满足条件时无响应
        if (!inGame()) {
            // 未在对局中
            return;
        }
        if (room.isTurn(this) || room.getChessCount() == 0 || room.isUndo(this)) {
            return;
        }
        // 设置请求悔棋的玩家名
        room.setUndoPlayer(this);
        // 给对手发送消息
        room.getRival(this).send(Action.requestedUndo, null);
    }
    
    /**
     * 处理悔棋请求（接受/拒绝）
     *
     * @param accept 是否接受
     */
    public void handleUndo(boolean accept) throws IOException {
        // 能够拒绝悔棋请求的条件是: 玩家在对局中且对方发起了悔棋请求
        // 不满足条件时无响应
        if (!inGame()) {
            // 未在对局中
            return;
        }
        if (!room.isUndo(room.getRival(this))) {
            return;
        }
        // 给对手发送消息
        Player rival = room.getRival(this);
        rival.send(Action.undoReplied, accept);
        if (accept) {
            room.retract();
        }
    }
    
    /**
     * 查看最后一个棋子
     */
    public void seeLastChess() throws IOException {
        // 最后一个棋子的坐标
        int[] coord = room.getLastCoord();
        send(Action.markChess, new HashMap<String, Integer>() {{
            put("x", coord[0]);
            put("y", coord[1]);
        }});
    }
    
    /**
     * 退出对局
     */
    public void leaveGame() throws IOException {
        // 退出对局的条件为在对局中
        // 不满足条件时无响应
        // 先获取 room，因为 room 随时可能因为房间关闭而变成 null
        Room gameRoom = this.room;
        if (!inGame()) {
            // 未在对局中
            return;
        }
        // 运行到了这里，则 gameRoom 一定不为空
        gameRoom.handleLeave(this);
    }
    
    /**
     * 观战
     *
     * @param roomId 房间号
     */
    public void watchGame(int roomId) throws IOException {
        // 能够观战的条件是玩家未在对局中且要进入的游戏房间存在
        // 不满足条件时向客户端发送提示消息
        // 若已在观战中则先退出观战 (leaveWatch()中含判断逻辑)
        leaveWatch();
        if (inGame()) {
            send(Action.showMessage, "请先完成当前对局");
            return;
        }
        Room room = Game.roomMap.get(roomId);
        if (room == null) {
            send(Action.showMessage, "该游戏房间不存在");
            return;
        }
        // 将玩家添加至房间的观战列表
        room.addWatch(this);
    }
    
    /**
     * 退出观战
     */
    public void leaveWatch() throws IOException {
        // 能够退出观战的条件为正在观战
        // 不满足条件时无响应
        if (isWatching()) {
            room.removeWatch(this);
        }
    }
    
    /**
     * 发送重绘消息
     */
    public void sendRepaint() throws IOException {
        if (room != null) {
            room.sendRepaint(this);
        }
    }
    
    /**
     * 是否正在对局
     *
     * @return 正在对局返回 true，否则返回 false
     */
    public boolean inGame() {
        return room != null && room.isGamer(this);
    }
    
    /**
     * 是否正在观战
     *
     * @return 正在观战返回 true，否则返回 false
     */
    public boolean isWatching() {
        return room != null && room.isWatcher(this);
    }
    
    /**
     * 是否可以与某玩家对局
     *
     * @param target 目标玩家
     * @return 返回 true 表示可对局，返回 false 表示不可对局
     */
    public boolean canPlayWith(Player target) throws IOException {
        if (inGame()) {
            send(Action.showMessage, "请先完成当前对局");
            return false;
        }
        if (target.inGame()) {
            send(Action.showMessage, "对方正在对局中，请稍后");
            return false;
        }
        return true;
    }
    
    /**
     * 增加对局数
     */
    public void addPlayCount() {
        System.out.println(name + " addPlayCount(" + id + ")");
        // 数据库操作
        try {
            userService.addPlayCount(id);
            JdbcUtils.commitAndClose();
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose();
            e.printStackTrace();
            // 将异常抛给 Tomcat 管理
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 增加获胜数
     */
    public void addWinCount() {
        System.out.println(name + " addWinCount(" + id + ")");
        // 数据库操作
        try {
            userService.addWinCount(id);
            JdbcUtils.commitAndClose();
        } catch (Exception e) {
            JdbcUtils.rollbackAndClose();
            e.printStackTrace();
            // 将异常抛给 Tomcat 管理
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 设置玩家状态为在线/离线
     *
     * @param online 是否在线
     */
    public void setOnline(boolean online) throws IOException {
        if (online) {
            Game.onlineCount++;
            Game.playerMap.put(name, this);
        } else {
            Game.onlineCount--;
            Game.playerMap.remove(name);
        }
        // 给所有玩家发送在线人数
        sendOnlineCount();
    }
    
    /**
     * 设置玩家状态为空闲/非空闲
     *
     * @param free    是否空闲 (不在对局中即为空闲)
     * @param players 玩家列表
     */
    public static void setFree(boolean free, Player... players) throws IOException {
        if (free) {
            for (Player p : players) {
                Game.freeList.add(p.name);
            }
        } else {
            for (Player p : players) {
                Game.freeList.remove(p.name);
            }
        }
        // 给空闲玩家发送可对战列表和房间列表
        sendFreeList();
        sendRoomList();
    }
    
    /**
     * 给空闲玩家发送可对战列表
     */
    private static void sendFreeList() throws IOException {
        for (String playerName : Game.freeList) {
            Player player = Game.playerMap.get(playerName);
            if (player != null) {
                // 获取空闲玩家列表（这里的 list 不能在循环外面获取，因为后面会 remove 玩家自身，对于每个玩家来说是不一样的）
                List<String> list = new ArrayList<>(Game.freeList);
                // 去掉玩家自身
                list.remove(playerName);
                player.send(Action.updateFreeList, list);
            }
        }
    }
    
    /**
     * 给空闲玩家发送房间列表
     */
    private static void sendRoomList() throws IOException {
        Set<Integer> idSet = Game.roomMap.keySet();
        for (String playerName : Game.freeList) {
            Player player = Game.playerMap.get(playerName);
            if (player != null) {
                player.send(Action.updateRoomList, idSet);
            }
        }
    }
    
    /**
     * 给所有玩家发送在线人数
     */
    private void sendOnlineCount() throws IOException {
        for (Player player : Game.playerMap.values()) {
            player.send(Action.updateOnlineCount, Game.onlineCount);
        }
    }
    
    /**
     * 向客户端发送消息
     *
     * @param action 执行的操作
     * @param data   消息数据
     */
    public synchronized void send(Action action, Object data) throws IOException {
        if (session.isOpen()) {
            session.getBasicRemote().sendText(new MsgInfo(action, data).toJson());
        }
    }
}
